﻿<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
	<head>
		<title>Client-Server Sync Tests</title>
		
		<link href="../Common/Styles/qunit.css" type="text/css" rel="stylesheet" />
		
		<script src="../Common/Scripts/JQuery/jquery-1.3.2.js" type="text/javascript"></script>
		<script src="../Common/Scripts/QUnit/qunit.js" type="text/javascript"></script>
		<script src="../Common/Scripts/Microsoft/MicrosoftAjax.js" type="text/javascript"></script>
		<script src="../Common/Scripts/ExoWeb/exoweb.js" type="text/javascript"></script>
		<script src="../Common/Scripts/QUnit/qunit.ext.js" type="text/javascript"></script>
		<script src="../Common/Scripts/ExoWeb/exoweb.model.js" type="text/javascript"></script>
		<script src="../Common/Scripts/ExoWeb/exoweb.mapper.js" type="text/javascript"></script>
		<script src="../Common/Scripts/ExoWeb/exoweb.mock.js" type="text/javascript"></script>
		<script src="../Common/Scripts/mock-driver.js" type="text/javascript"></script>
		<script src="ChangeSet.js" type="text/javascript"></script>
		
		<script type="text/javascript">
			//ExoWeb.trace.flags.sync = true;
			//ExoWeb.trace.flags.mocks = true;
			ExoWeb.Mock.objectProviderDelay = 0;
			ExoWeb.Mock.typeProviderDelay = 0;
			ExoWeb.Mock.roundtripProviderDelay = 0;
			ExoWeb.Mock.simulateLazyLoading = false;

			// HACK: signal behavior that temporarily releases control conflicts with synchronous nature of tests
			ExoWeb.Signal.prototype._doCallback = function Signal$_doCallback(name, thisPtr, callback, args) {
				try {
					callback.apply(thisPtr, args || []);
				}
				catch (e) {
					ExoWeb.trace.logError("signal", "({0}) {1} callback threw an exception: {2}", [this._debugLabel, name, e]);
				}
			};

			var context = ExoWeb.context({ model: { driver: { id: "1", from: "Driver" } } });

			setupTest("test1", { description: "Apply Init with ID Translation", expect: 10 }, function() {
				var undefined;

				equals(Dealer.meta.get("+c0"), undefined, "Dealer should not exist before init");

				context.server.apply(new ChangeSet().init("Dealer", "?1").build());

				var newDealer = Dealer.meta.get("+c0");
				ok(newDealer, "Dealer should exist after init by client generated id");
				
				// check that id translation is setup
				equals(context.server._translator.forward("Dealer", "+c0"), "?1", "Should be able to translate from client id to server id");
				equals(context.server._translator.reverse("Dealer", "?1"), "+c0", "Should be able to translate from server id to client id");
				
				// inpect the change log
				var changes = context.server.get_Changes();
				equals(changes.length, 1, "We should have 1 change in the log");
				equals(changes[0].__type, "InitNew:#ExoGraph", "It should be an init change");
				equals(changes[0].instance.id, "?1", "The id should be the server id, NOT the client id");

				newDealer.set_Name("Cameron");
				changes = context.server.get_Changes();
				equals(changes.length, 2, "We should now have 2 change in the log");
				equals(changes[1].__type, "ValueChange:#ExoGraph", "The most recent should be a value change");
				equals(changes[1].instance.id, "?1", "The id should be the server id, NOT the client id");
			});

			setupTest("test2", { description: "Apply Reference Property Change", expect: 2 }, function() {
				equals(context.model.driver.get_Owner().get_Location().meta.id, "1", "Driver's owner's location should be 1");

				context.server.apply(new ChangeSet().ref("CarOwner", "1", "Location", "OwnerLocation", "1", "2").build());

				equals(context.model.driver.get_Owner().get_Location().meta.id, "2", "Driver's owner's location should be 2");
			});

			setupTest("test3", { description: "Apply Value Property Change", expect: 2 }, function() {
				equals(context.model.driver.get_Owner().get_Location().get_Name(), "Work", "Driver's owner's location name should be Work");

				context.server.apply(new ChangeSet().val("OwnerLocation", "2", "Name", "Work", "Play").build());

				equals(context.model.driver.get_Owner().get_Location().get_Name(), "Play", "Driver's owner's location name should be Play");
			});

			setupTest("test4", { description: "Apply List Property Changes", expect: 13 }, function() {
				equals(context.model.driver.get_Cars().length, 2, "Driver should have 2 cars");
				equals(context.model.driver.get_Cars()[0].get_Name(), "Sentra", "First car is Sentra");
				equals(context.model.driver.get_Cars()[1].get_Name(), "Bike", "Second car is Bike");

				context.server.apply(new ChangeSet().delRef("Driver", "1", "Cars", "Car", "2").build());
				equals(context.model.driver.get_Cars().length, 1, "Driver should now have 1 car");
				equals(context.model.driver.get_Cars()[0].get_Name(), "Sentra", "First car is Sentra");

				context.server.apply(new ChangeSet().addRef("Driver", "1", "Cars", "Car", "3").build());
				equals(context.model.driver.get_Cars().length, 2, "Driver should now have 2 cars");
				equals(context.model.driver.get_Cars()[1].get_Name(), "Tank", "Second car is Tank");

				// Test batch operations.

				var car1 = Car.meta.get("1");
				var car3 = Car.meta.get("3");

				var numUpdates = 0;
				Driver.$Cars.addChanged(function() { numUpdates++; }, context.model.driver);
				context.server.apply(new ChangeSet().delRef("Driver", "1", "Cars", "Car", "1").delRef("Driver", "1", "Cars", "Car", "3").build());
				equals(context.model.driver.get_Cars().length, 0, "Driver should now have 0 car");
				equals(numUpdates, 1, "A single update should have been recorded.");

				numUpdates = 0;
				context.server.apply(new ChangeSet().addRef("Driver", "1", "Cars", "Car", "1").addRef("Driver", "1", "Cars", "Car", "3").build());
				equals(context.model.driver.get_Cars().length, 2, "Driver should now have 2 cars");
				equals(context.model.driver.get_Cars()[0].get_Name(), "Sentra", "First car is Sentra");
				equals(context.model.driver.get_Cars()[1].get_Name(), "Tank", "Second car is Tank");
				equals(numUpdates, 1, "A single update should have been recorded.");
			});

			setupTest("test5", { description: "Client-Server Changes from Server", expect: 4 }, function() {
				var bryan = Driver.meta.get("1");
				var bob = CarOwner.meta.get("1");
				var joe = CarOwner.meta.get("2");

				equals(bob.get_Drivers().length, 1, "Bob should have one driver");
				equals(joe.get_Drivers().length, 0, "Joe should have no drivers");

				bryan.set_Owner(joe);

				ExoWeb.Mock.roundtrip(function(type, id, changes) {
					// mock server behavior of updating Location.Owners list
					return new ChangeSet()
						.delRef("CarOwner", bob.meta.id, "Drivers", "Driver", bryan.meta.id)
						.addRef("CarOwner", joe.meta.id, "Drivers", "Driver", bryan.meta.id)
						.build();
				});

				context.server.roundtrip();

				equals(bob.get_Drivers().length, 0, "Bob should now have zero drivers");
				equals(joe.get_Drivers().length, 1, "Joe should now have one driver");
			});

			setupTest("test6", { description: "Client-Server Commit (Changing Ids)", expect: 13 }, function() {
				var sam = new Driver();
				var samLegacyId = sam.meta.id;

				// Simulate no changes on server.
				ExoWeb.Mock.roundtrip(null);

				context.server.roundtrip();

				equals(sam.meta.id, samLegacyId, "Sam's id should still be the temporary id");

				var samNewId = "1000";

				// Simulate commit behavior with id change.
				ExoWeb.Mock.save(function(root, changes) {
					return new ChangeSet().save([{ type: "Driver", oldId: samLegacyId, newId: samNewId}]).build();
				});

				context.server.save(context.model.driver);

				var changes = context.server.get_Changes();
				equals(changes.length, 0, "Queue should be empty after commit");

				equals(sam.meta.id, samNewId, "Sam's id should be a new permanent id");
				equals(Driver.meta.get(samLegacyId), sam, "Sam can still be retieved by the old id");
				equals(Driver.meta.get(samNewId), sam, "Sam can also be retieved by the new id");

				ok(Driver.meta._pool[samNewId], "Sam's new id is a key in the Driver pool");
				ok(!Driver.meta._legacyPool[samNewId], "Sam's new id is not a key in the Driver legacy pool");
				ok(Driver.meta._legacyPool[samLegacyId], "Sam's legacy id is a key in the Driver legacy pool");
				ok(!Driver.meta._pool[samLegacyId], "Sam's legacy id is not a key in the Driver pool");

				ok(Person.meta._pool[samNewId], "Sam's new id is a key in the Person pool");
				ok(!Person.meta._legacyPool[samNewId], "Sam's new id is not a key in the Person legacy pool");
				ok(Person.meta._legacyPool[samLegacyId], "Sam's legacy id is a key in the Person legacy pool");
				ok(!Person.meta._pool[samLegacyId], "Sam's legacy id is not a key in the Person pool");

				// Account for transaction containing events with persisted ids prior to save event.

			});

			setupTest("test7", { description: "Client-Server Commit (Changing Ids With Prior Transactions)", expect: 3 }, function() {
				// Simulate new object created on server.
				ExoWeb.Mock.roundtrip(function() {
					return new ChangeSet().init("Driver", "?2").val("Driver", "?2", "Name", null, "Casey").build();
				});

				context.server.roundtrip();

				var caseyClientId = context.server._translator.reverse("Driver", "?2");
				var casey = Driver.meta.get(caseyClientId);

				var caseyLegacyId = "?2";
				var caseyNewId = "2000";
				var dateCreated = new Date();

				// Simulate commit behavior with changes referencing persisted id before save event.
				ExoWeb.Mock.save(function(root, changes) {
					return new ChangeSet()
						.val("Driver", caseyNewId, "DateCreated", null, dateCreated)
						.save([{ type: "Driver", oldId: caseyLegacyId, newId: caseyNewId}]).build();
				});

				context.server.save(casey);

				var changes = context.server.get_Changes();
				equals(changes.length, 0, "Queue should be empty after commit");

				equals(casey.meta.id, caseyNewId, "Casey's id should be a new permanent id");
				equals(casey.get_DateCreated().valueOf(), dateCreated.valueOf(), "Date created should have been set properly.");
			});

			setupTest("test8", { description: "Initialized Objects", expect: 3 }, function () {
				var undefined;

				var newDriver2 = new Driver();
				equals(ExoWeb.Model.LazyLoader.isLoaded(newDriver2), true, "Object should NOT be lazy-loadable since no id was supplied (new object)");

				var numChanges = context.server._changes.length;

				var newDriver1 = new Driver("5", { Name: "John Doe" });
				equals(ExoWeb.Model.LazyLoader.isLoaded(newDriver1), false, "Object should be lazy-loadable since an id was supplied (existing object)");
				equals(context.server._changes.length, numChanges, "Number of changes should be the same, since the object was existing and properties were initialized");
			});

			timeoutTests(5000);

			context.ready(function () {
				executeTest("test1");
				executeTest("test2");
				executeTest("test3");
				executeTest("test4");
				executeTest("test5");
				executeTest("test6");
				executeTest("test7");
				executeTest("test8");
			});
		</script>
	</head>
	<body xmlns:sys="javascript:Sys" xmlns:dataview="javascript:Sys.UI.DataView" sys:activate="*">
		
		<!-- QUnit Display -->
		<h1 id="qunit-header">Test Results:</h1>
		<h2 id="qunit-banner"></h2>
		<h2 id="qunit-userAgent"></h2>
		<ol id="qunit-tests"></ol>
	</body>
</html>
